import { Subject, fromEvent, of} from 'rxjs';
import { map, mergeAll } from 'rxjs/operators';
import { setCanvasSize, createBlockImage } from '../../../utils/tools';
import { ColorHelper } from '../../../utils/color-helper';
import './color-picker.less';
import { EventHelper } from '../../../utils/event-helper';

type TRBG = { r: number; g: number; b: number };
type TRBGA = TRBG & {a: number}
type TSize = {width: number; height: number};
function RgbObjToArr(rgb: TRBG): [number, number, number] {
    return [rgb.r, rgb.g, rgb.b];
}
const DOMS = `
    <div style="position: relative;">
        <canvas class="z-color-panel-picker"></canvas>
        <span class="z-point"></span>
    </div>
    <div class="z-color-panel-args-area">
        <div class="z-color-panel-bar-area">
            <div class="z-scroll-area z-base-color">
                <span class="z-bar"></span>
                <canvas class="z-color-canvas"></canvas>
            </div>
            <div class="z-scroll-area z-opacity-color">
                <span class="z-bar"></span>
                <canvas class="z-color-canvas"></canvas>
            </div>
        </div>
        <div class="z-current-color-show">
        </div>
    </div>
    <div class="z-present-color-value">
        <div class="z-value z-hex">
            <input />
            <label>
                HEX
            </label>
        </div>
        <div class="z-value z-rgba z-rgba-r">
            <input />
            <label>
                R
            </label>
        </div>
        <div class="z-value z-rgba z-rgba-g">
            <input />
            <label>
                G
            </label>
        </div>
        <div class="z-value z-rgba z-rgba-b">
            <input />
            <label>
                B
            </label>
        </div>
        <div class="z-value z-rgba z-rgba-a">
            <input />
            <label>
                A
            </label>
        </div>
    </div>
`;
export class ColorPicker {
    protected opt: IOption;
    protected container: HTMLElement;
    protected currentReferenceRgb: TRBG;
    protected currentOpacity: number;
    protected selectColorChange: Subject<TRBG> = new Subject();

    protected emptyImgBase64: string = '';

    protected colorPanelSize: TSize;
    protected colorPanelEle: HTMLCanvasElement;
    protected colorPanelCtx: CanvasRenderingContext2D;

    protected opacityPannelEle: HTMLCanvasElement;
    protected opacityPannelCtx: CanvasRenderingContext2D;
    protected opacityPannelSize: TSize;

    protected fullColorBarEle: HTMLElement;

    protected inputValueGroup: {r: number, g: number, b: number, a: number};
    protected inputValueGroupProxy: {r: number, g: number, b: number, a: number};

    public selectColorChange$: Subject<{hex: string, r: number, g: number, b: number, a: number}> = new Subject();
    constructor() {
        // this.init();
        this.container = document.createElement('div');
        this.container.className = 'z-color-panel';
        this._init();
    }
    protected _init(rgba: {r: number, g: number, b: number, a: number} = {r: 255, g: 1, b: 0, a: 1}) {
        this.currentReferenceRgb = rgba;
        this.inputValueGroup = {
            r: this.currentReferenceRgb.r, 
            g: this.currentReferenceRgb.g,
            b: this.currentReferenceRgb.b,
            a: rgba.a
        }
        
        this.container.innerHTML = DOMS;

        this.colorPanelEle = this.container.querySelector('.z-color-panel-picker') as HTMLCanvasElement;
        this.colorPanelCtx = this.colorPanelEle.getContext('2d');
        
        this.opacityPannelEle = this.container.querySelector('.z-opacity-color').querySelector('.z-color-canvas');
        this.opacityPannelCtx = this.opacityPannelEle.getContext('2d');
    }
    public init(rgba: {r: number, g: number, b: number, a: number} = {r: 255, g: 1, b: 0, a: 1}) {
        this.colorPanelSize = setCanvasSize(this.colorPanelEle); 
        this.opacityPannelSize = {
            width: this.container.querySelector('.z-opacity-color').clientWidth,
            height: this.container.querySelector('.z-opacity-color').clientHeight,
        }
        this.opacityPannelEle.width = this.opacityPannelSize.width;
        this.opacityPannelEle.height = this.opacityPannelSize.height;
        this.emptyImgBase64 = createBlockImage(this.opacityPannelSize.height / 3, this.opacityPannelSize.height / 3);
        this.container.querySelector('.z-opacity-color').setAttribute('style', `background: url(${this.emptyImgBase64})`);
        this.drawFullColor();
        this.draw();
        
        this.setFullColorBar();
        this.fullColorBarChange();
        this.opacityBarChange();
        this.inputEvents();
        this.bindClickPanelEvent();
        this.init = () => {};
    }
    protected bindClickPanelEvent():void {
        fromEvent<MouseEvent>(this.colorPanelEle, 'click')
        .subscribe(evt => {
            try {
                let x = evt.offsetX / this.colorPanelEle.clientWidth;
                let y = evt.offsetY / this.colorPanelEle.clientHeight;
                let hsv = ColorHelper.RGBToHSV(RgbObjToArr(this.currentReferenceRgb));
                let h = hsv[0];
                let s = hsv[1] * x;
                let v = hsv[2] * (1 - y);
                let newRgb = ColorHelper.HSVToRGB([h,s,v]);
                this.inputValueGroupProxy.r = newRgb[0];
                this.inputValueGroupProxy.g = newRgb[1];
                this.inputValueGroupProxy.b = newRgb[2];      
                this.selectColorChange$.next({
                    hex: ColorHelper.RGBToHex([this.inputValueGroup.r, this.inputValueGroup.g, this.inputValueGroup.b]),
                    r: this.inputValueGroup.r,
                    g: this.inputValueGroup.g,
                    b: this.inputValueGroup.b,
                    a: this.inputValueGroup.a
                });          
            } catch (error) {
                
            }

        });
    }
    protected inputEvents():void {
        let inputGroupContainer = this.container.querySelector('.z-present-color-value');
        let showColorEle = this.container.querySelector('.z-current-color-show') as HTMLInputElement;
        let inputHex = inputGroupContainer.querySelector('.z-hex').querySelector('input') as HTMLInputElement;
        let inputR = inputGroupContainer.querySelector('.z-rgba-r').querySelector('input') as HTMLInputElement;
        let inputG = inputGroupContainer.querySelector('.z-rgba-g').querySelector('input') as HTMLInputElement;
        let inputB = inputGroupContainer.querySelector('.z-rgba-b').querySelector('input') as HTMLInputElement;
        let inputA = inputGroupContainer.querySelector('.z-rgba-a').querySelector('input') as HTMLInputElement;
        let container = this.container.querySelector('.z-opacity-color');
        let bar = container.querySelector('.z-bar') as HTMLElement;
        let valueProxy = new Proxy(this.inputValueGroup, {
            get: (target, name) => {
                let resVlue: number = undefined;
                switch(name) {
                    case 'r':
                        resVlue = target.r;
                        break;
                    case 'g':
                        resVlue = target.g;
                        break;
                    case 'b':
                        resVlue = target.b;
                        break;
                    case 'a':
                        resVlue = target.a * 100;
                        break;
                    default: 
                        console.warn(`get invalid attribute! ${String(name)}`);
                        return undefined;
                }
                return resVlue;
            },
            set: (target, name, value) => {
                switch(name) {
                    case 'r':
                        target.r = value;
                        break;
                    case 'g':
                        target.g = value;
                        break;
                    case 'b':
                        target.b = value;
                        break;
                    case 'a':
                        target.a = value;
                        break;
                    default: 
                        console.log(`input invalid attribute! ${String(name)}`);
                        return undefined;
                }
                inputHex.value = ColorHelper.RGBToHex([target.r, target.g, target.b]);

                inputA.value = String(Math.round(target.a * 100));
                inputB.value = String(target.b);
                inputG.value = String(target.g);
                inputR.value = String(target.r);
                bar.style.left = (target.a * container.clientWidth).toFixed(2) + 'px';
                showColorEle.style.backgroundColor = `rgba(${target.r},${target.g},${target.b},${target.a})`;
                this.setPoint();
                this.drawOpacity();
                return true;
            }
        });
        this.inputValueGroupProxy = valueProxy;

        let hex$ = fromEvent<any>(inputHex, 'input')
        .pipe(map((evt: any) => {
            return "#eeeeee";
        }))
        .pipe(map(res => {
            let rgb = ColorHelper.HexToRGB(res);
            valueProxy.r = rgb[0];
            valueProxy.g = rgb[1];
            valueProxy.b = rgb[2];
        }));


        let r$ = fromEvent<any>(inputR, 'input')
        .pipe(map((evt: any) => {
            return Number(inputR.value);
        }))
        .pipe(map(res => {
            valueProxy.r = res;
        }));


        let g$ = fromEvent<any>(inputG, 'input')
        .pipe(map((evt: any) => {
            return Number(inputG.value);
        }))
        .pipe(map(res => {
            valueProxy.g = res;
        }));

        let b$ = fromEvent<any>(inputB, 'input')
        .pipe(map((evt: any) => {
            return Number(inputB.value);
        }))
        .pipe(map(res => {
            valueProxy.b = res;
        }));

        of(hex$, r$, g$, b$)
        .pipe(mergeAll())
        .subscribe(() => {
            this.currentReferenceRgb = {
                r: this.inputValueGroupProxy.r,
                g: this.inputValueGroupProxy.g,
                b: this.inputValueGroupProxy.b
            }
            this.setFullColorBar();
            this.draw();
        });

        fromEvent<any>(inputA, 'input')
        .pipe(map((evt: any) => {
            return Number(inputA.value);
        }))
        .subscribe(res => {
            valueProxy.a = Number((res / 100).toFixed(2));
        });
        valueProxy.b = this.inputValueGroup.b;
        valueProxy.r = this.inputValueGroup.r;
        valueProxy.g = this.inputValueGroup.g;
        valueProxy.a = this.inputValueGroup.a;
    }
    protected draw(): void {
        this.colorPanelCtx.clearRect(0,0, this.colorPanelSize.width, this.colorPanelSize.height);
        let hsv = ColorHelper.RGBToHSV(RgbObjToArr(this.currentReferenceRgb));
        let h = Math.round(hsv[0]);
        let dd = 110;
        let p = 100 * 100 / dd / this.colorPanelSize.height;
        for(let i = 0; i <= this.colorPanelSize.height - 0; i++) {
            try {
                let val = Math.ceil(100 - (i / this.colorPanelSize.height) * 100);
                let leftRgb = ColorHelper.HSVToRGB([h, 1, val]);
                let rightRgb = ColorHelper.HSVToRGB([h, 100, val]);
                this.colorPanelCtx.save();
                let lineGradient = this.colorPanelCtx.createLinearGradient(0, i, this.colorPanelSize.width, i);
                lineGradient.addColorStop(0, "#" + ColorHelper.RGBToHex(leftRgb));
                lineGradient.addColorStop(1, "#" + ColorHelper.RGBToHex(rightRgb));
                this.colorPanelCtx.strokeStyle = lineGradient;
                this.colorPanelCtx.beginPath();
                this.colorPanelCtx.moveTo(0, i);
                this.colorPanelCtx.lineTo(this.colorPanelSize.width, i);
                this.colorPanelCtx.stroke();
                this.colorPanelCtx.closePath();
                this.colorPanelCtx.restore();                
            } catch (error) {
                console.log(i)
                throw new Error(error)
            }

        }
        this.drawOpacity();
        this.setPoint();
    }
    protected setPoint(): void {
        let pointEle = this.container.querySelector('.z-point') as HTMLElement;
        let hsv = ColorHelper.RGBToHSV(RgbObjToArr(this.inputValueGroup));
        let top = (100 - hsv[2]) / 100 * this.colorPanelEle.clientHeight - pointEle.clientHeight / 2;
        let left = hsv[1] / 100 * this.colorPanelEle.width - pointEle.clientWidth / 2;
        pointEle.style.left = String(left) + 'px';
        pointEle.style.top = String(top) + 'px';
    }
    protected drawOpacity(): void {
        this.opacityPannelCtx.clearRect(0, 0, this.opacityPannelEle.width, this.opacityPannelEle.height);
        this.opacityPannelCtx.lineWidth = this.opacityPannelEle.height;
        let rgb = RgbObjToArr(this.inputValueGroup)
        let lineGradient = this.opacityPannelCtx.createLinearGradient(0, 0, this.opacityPannelEle.width, this.opacityPannelEle.height);
        lineGradient.addColorStop(0, `rgba(${rgb[0]}, ${rgb[1]}, ${rgb[2]}, 0)`);
        lineGradient.addColorStop(1, `rgba(${rgb[0]}, ${rgb[1]}, ${rgb[2]}, 1)`);
        this.opacityPannelCtx.strokeStyle = lineGradient;
        this.opacityPannelCtx.beginPath();
        this.opacityPannelCtx.moveTo(0, this.opacityPannelEle.height / 2);
        this.opacityPannelCtx.lineTo(this.opacityPannelEle.width, this.opacityPannelEle.height / 2);
        this.opacityPannelCtx.stroke();
        this.opacityPannelCtx.closePath();
        this.opacityPannelCtx.restore();
    }
    protected drawFullColor(): void {
        let container = this.container.querySelector('.z-base-color');
        let canvas = container.querySelector('.z-color-canvas') as HTMLCanvasElement;
        let ctx = canvas.getContext('2d');
        canvas.width = container.clientWidth;
        canvas.height = container.clientHeight;
        let lineGradient = ctx.createLinearGradient(0, 0, canvas.width,canvas.height);
        ctx.lineWidth = canvas.height;
        lineGradient.addColorStop(0, "RGB(255, 0, 0)");
		lineGradient.addColorStop(0.17, "RGB(255, 255, 0)");
		lineGradient.addColorStop(0.34, "RGB(0, 255, 0)");
		lineGradient.addColorStop(0.51, "RGB(0, 255, 255)");
		lineGradient.addColorStop(0.68, "RGB(0, 0, 255)");
		lineGradient.addColorStop(0.85, "RGB(255, 0, 255)");
        lineGradient.addColorStop(1, "RGB(255, 0, 0)");
        ctx.strokeStyle = lineGradient;
        ctx.beginPath();
        ctx.moveTo(0, canvas.height / 2);
        ctx.lineTo(canvas.width, canvas.height / 2);
        ctx.stroke();
        ctx.closePath();
        ctx.restore();
    }
    protected setFullColorBar(): void {
        let container = this.container.querySelector('.z-base-color');
        let bar = container.querySelector('.z-bar') as HTMLElement;
        let t = [this.currentReferenceRgb.r, this.currentReferenceRgb.g, this.currentReferenceRgb.b];
        if(this.currentReferenceRgb.r >= this.currentReferenceRgb.g && this.currentReferenceRgb.r >= this.currentReferenceRgb.b) {
            if(this.currentReferenceRgb.g >= this.currentReferenceRgb.b) {
                bar.style.left = String(container.clientWidth * 0.17 - container.clientWidth * 0.17 * (1 - (this.currentReferenceRgb.g / 255))) + 'px';
            } else {
                bar.style.left = String(container.clientWidth * 0.85 + (0.15 * container.clientWidth) * (1 - (this.currentReferenceRgb.b / 255))) + 'px';
            }
            return;
        }
        if(this.currentReferenceRgb.g >= this.currentReferenceRgb.r && this.currentReferenceRgb.g >= this.currentReferenceRgb.b) {
            if(this.currentReferenceRgb.r >= this.currentReferenceRgb.b) {
                bar.style.left = String(container.clientWidth * 0.34 - (this.currentReferenceRgb.r / 255) * 0.17 * container.clientWidth) + 'px';
            } else {
                bar.style.left = String(container.clientWidth * 0.51 - (this.currentReferenceRgb.b / 255) * (0.51 - 0.34) * container.clientWidth) + 'px';
            }
            return;
        }
        if(this.currentReferenceRgb.r >= this.currentReferenceRgb.g) {
            bar.style.left = String(container.clientWidth * 0.85 - (this.currentReferenceRgb.r / 255) * (0.85 - 0.68) * container.clientWidth) + 'px';
        } else {
            bar.style.left = String(container.clientWidth * 0.68 - (this.currentReferenceRgb.g / 255) * (0.68 - 0.51) * container.clientWidth) + 'px';
        }
    }
    protected fullColorBarChange() {
        let container = this.container.querySelector('.z-base-color');
        let bar = container.querySelector('.z-bar') as HTMLElement;
        EventHelper.move(
            bar,
            (evt: ZMouseEvent) => {
                let left = Number(evt.target.style.left.replace('px', ''));
                return {left: left, clientX: evt.clientX}
            },
            () => {},
            (position: {left: number, clientX: number}, evt: ZMouseEvent) => {
                let left = position.left + evt.clientX - position.clientX;
                let activeLeft = left;
                if(left > container.clientWidth) {
                    activeLeft = container.clientWidth;
                }
                if(left < 0) {
                    activeLeft = 0;
                }
                bar.style.left = String(activeLeft) + 'px';
                let per = left / container.clientWidth;
                if(per >= 0 && per <= 0.17) {
                    let p1 = left / (0.17 * container.clientWidth) * 255;
                    this.currentReferenceRgb = { r: 255, g: p1, b: 0};
                    this.draw();
                }
                if(per > 0.17 && per <= 0.34) {
                    let p1 = (left - 0.17 * container.clientWidth) / ((0.34 - 0.17) * container.clientWidth) * 255;
                    this.currentReferenceRgb = { r: 255 - p1, g: 255, b: 0};
                    this.draw();
                }
                if(per > 0.34 && per <= 0.51) {
                    let p1 = (left - 0.34 * container.clientWidth) / ((0.51 - 0.34) * container.clientWidth) * 255;
                    this.currentReferenceRgb = { r: 0, g: 255, b: p1};
                    this.draw();
                }
                if(per > 0.51 && per <= 0.68) {
                    let p1 = (left - 0.51 * container.clientWidth) / ((0.68 - 0.51) * container.clientWidth) * 255;
                    this.currentReferenceRgb = { r: 0, g: 255 - p1, b: 255};
                    this.draw();
                }
                if(per > 0.68 && per <= 0.85) {
                    let p1 = (left - 0.68 * container.clientWidth) / ((0.85 - 0.68) * container.clientWidth) * 255;
                    this.currentReferenceRgb = { r: p1, g: 0, b: 255};
                    this.draw();
                }
                if(per > 0.85) {
                    let p1 = (left - 0.85 * container.clientWidth) / ((1 - 0.85) * container.clientWidth) * 255;
                    this.currentReferenceRgb = { r: 255, g: 0, b: 255 - p1};
                    this.draw();
                }
                let event = this.colorPanelEle.ownerDocument.createEvent('MouseEvents');
                let ele = this.container.querySelector('.z-point') as HTMLElement;
                let top = Number(ele.style.top.replace('px','')) + 4;
                let lf = Number(ele.style.left.replace('px','')) + 4;
                let x = lf / this.colorPanelEle.clientWidth;
                let y = top / this.colorPanelEle.clientHeight;
                let hsv = ColorHelper.RGBToHSV(RgbObjToArr(this.currentReferenceRgb));
                let h = hsv[0];
                let s = hsv[1] * x;
                let v = hsv[2] * (1 - y);
                let newRgb = ColorHelper.HSVToRGB([h,s,v]);
                this.inputValueGroupProxy.r = newRgb[0];
                this.inputValueGroupProxy.g = newRgb[1];
                this.inputValueGroupProxy.b = newRgb[2];   
                // https://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-Event-initMouseEvent
                // console.log(top,lf)
                // let opts: any = {
                //     type: 'click',
                //     canBubble: true,
                //     cancelable: true,
                //     view: this.colorPanelEle.ownerDocument.defaultView,
                //     detail: 1,
                //     screenX: lf, //The coordinates within the entire page
                //     screenY: top,
                //     clientX: lf, //The coordinates within the viewport
                //     clientY: top,
                //     ctrlKey: false,
                //     altKey: false,
                //     shiftKey: false,
                //     metaKey: false, //I *think* 'meta' is 'Cmd/Apple' on Mac, and 'Windows key' on Win. Not sure, though!
                //     button: 0, //0 = left, 1 = middle, 2 = right
                //     relatedTarget: null,
                // }
                // event.initMouseEvent(
                //     opts.type,
                //     opts.canBubble,
                //     opts.cancelable,
                //     opts.view,
                //     opts.detail,
                //     opts.screenX,
                //     opts.screenY,
                //     opts.clientX,
                //     opts.clientY,
                //     opts.ctrlKey,
                //     opts.altKey,
                //     opts.shiftKey,
                //     opts.metaKey,
                //     opts.button,
                //     opts.relatedTarget
                // )
                // console.log(event)
                // this.colorPanelEle.dispatchEvent(event);
            }
        );
    }
    protected opacityBarChange() {
        let container = this.container.querySelector('.z-opacity-color');
        let bar = container.querySelector('.z-bar') as HTMLElement;
        EventHelper.move(
            bar,
            (evt: ZMouseEvent) => {
                let left = Number(evt.target.style.left.replace('px', ''));
                return {left: left, clientX: evt.clientX}
            },
            () => {},
            (position: {left: number, clientX: number}, evt: ZMouseEvent) => {
                let left = position.left + evt.clientX - position.clientX;
                let activeLeft = left;
                if(left > container.clientWidth) {
                    activeLeft = container.clientWidth;
                }
                if(left < 0) {
                    activeLeft = 0;
                }
                this.inputValueGroupProxy.a = Number((activeLeft / container.clientWidth).toFixed(2));
                bar.style.left = String(activeLeft) + 'px';
            }
        );
    }
    public getContainerDom() {
        return this.container;
    }
}
type ZMouseEvent = MouseEvent & {target: HTMLElement};
interface IOption {
    referenceEleIsHorizontal: boolean;
    opacityEleIsHorizontal: boolean;
    referenceBarEle: HTMLElement;
    opacityBarEle: HTMLElement;
}